EC2(AL2023)でヘッドレスChromeを動かしてみた

EC2(AL2023)でヘッドレスChromeを動かしてみた

curlコマンドのように実行してみます
Clock Icon2024.08.26

こんにちは、なおにしです。

検証のために静的サイトのコンテンツを扱っていた時、ふとJavaScriptの実行結果をコマンドで取得したくなりました。ヘッドレスブラウザを使用したことが無かったので、実際にインストールして使ってみるまでをまとめてみました。

やりたかったこと

例えば、以下のような簡単な応答を返す静的サイトがあったとします。

20240826_naonishi_headless-chrome-on-al2023_1.jpg

このサイトは以下のHTMLとJavaScriptの2ファイルで構成されています。

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>アクセス情報</title>
</head>
<body>
    <div id="access-info"></div>
    <script src="access-info.js"></script>
</body>
</html>
access-info.js
document.addEventListener('DOMContentLoaded', function() {
    const accessInfo = document.getElementById('access-info');

    // アクセス日時
    const dateInfo = `
        <p>アクセス日時</p>
        <p>${new Date().toLocaleString()}</p>
    `;

    // IPアドレス
    fetch('https://api.ipify.org?format=json')
        .then(response => response.json())
        .then(data => {
            const ipInfo = `
                <p>IPアドレス</p>
                <p>${data.ip}</p>
            `;
            accessInfo.innerHTML = dateInfo + ipInfo;
        })
        .catch(error => {
            console.error('Error:', error);
            accessInfo.innerHTML = dateInfo;
        });
});

アクセス元IPアドレスをレスポンスとして返すために、JavaScript内で外部APIからデータを取得するようになっています。

コマンドで取得したいのはWebブラウザでアクセスした時の情報(アクセス日時とIPアドレス)ですが、例えばcurlコマンドを使用すると以下のようになります。

20240826_naonishi_headless-chrome-on-al2023_2.jpg

curlではJavaScriptを実行していないので、結果として上記のようにindex.html の内容が返ってくるのは当然です。

APIに対して直接リクエストを送信した結果を得るわけでもなく、今回のようにあくまでブラウザに表示された結果(JavaScriptがDOMを通じてHTMLを操作した結果)をCLIで取得するのであれば、ヘッドレスブラウザを使用する必要があります。

例えばGoogle Chromeでも、以下のとおり2017年からヘッドレスブラウザとしての動作を提供しています。

https://developer.chrome.com/docs/chromium/new-headless

というわけで、実際にEC2のAmazon Linux 2023(AL2023) にChrome をインストールしてヘッドレスブラウザとしてデータを取得してみました。

やってみた

Amazon Linux 2(AL2) であれば EPEL リポジトリを追加してyumでChromiumをインストールするという流れですぐに試せそうでしたが、AL2023ではEPELをサポートしていないので別の方法を検討する必要がありました。

用途は異なりますが以下のサイトに同様の趣旨の記載とChrome のインストール手順があったため参考にします。

https://cloud.google.com/looker/docs/best-practices/how-to-install-chromium-for-amazon-linux?hl=ja

Amazon Linux などの一部の Linux ディストリビューションでは Chromium のインストールが困難です。

$ wget https://dl.google.com/linux/direct/google-chrome-stable_current_x86_64.rpm
$ sudo dnf install ./google-chrome-stable_current_x86_64.rpm

google-chromeコマンドで実行できるようになります。

20240826_naonishi_headless-chrome-on-al2023_3.png

前述のヘッドレスChromeに関するサイトに記載の内容を参考に、インストールしたChrome ヘッドレスブラウザとして実行してみます。

$ google-chrome --headless --disable-gpu --dump-dom https://example.com/

20240826_naonishi_headless-chrome-on-al2023_4.jpg

結果が取得できましたが、何やらエラーが出力されており、取得できた情報もcurlと同じです。

まず、エラーについてはいずれも「UPower」パッケージに関連しているもののようです。このパッケージはAL2には含まれていましたがAL2023には含まれていないパッケージの一つです。

https://docs.aws.amazon.com/ja_jp/linux/al2023/release-notes/removed-AL2023.5-AL2.html

AL2023はFedoraをベースとしたディストリビューションであるため、少しだけこちらからのパッケージインストールを試みましたが、依存関係やバージョン周りがまあまあ複雑だったため諦めました。。
ヘッドレスChrome自体は(エラー出力はあるものの)動いているため今回はエラー解消については割愛させていただきます。

というわけで実行結果がcurlと同じになってしまっていることを考えます。

ヘッドレスChromeの実行オプションを確認していくと、--virtual-time-budgetという項目がありました。説明内容は以下のとおりです。

--virtual-time-budget は、時間に依存するコード (たとえば、setTimeout/setInterval) の「早送り」として機能します。これにより、ブラウザーはページのコードを可能な限り高速に実行するように強制され、その時間が実際に経過したとページに信じ込ませます。

実際にGUIのWebブラウザでサンプルのような静的サイトを開いたときも、JavaScriptが読み込まれるまでの時間がわずかながらも存在すると考えると、こちらのオプションを適用するのが良さそうです。
このため--virtual-time-budgetに100ミリ秒を設定して、ついでに標準エラー出力を捨てて実行してみます。

$ google-chrome --headless --disable-gpu --virtual-time-budget=100 --dump-dom https://example.com/ 2>/dev/null

想定どおり取得することができました。

20240826_naonishi_headless-chrome-on-al2023_5.jpg

なお、1ミリ秒を設定して何度か実行すると、--virtual-time-budgetを設定していないパターンと同じ結果が出力されることがありましたので、JavaScriptの実行に時間がかかる場合は調整が必要です。

補足

Mac で Google Chrome をインストールしている場合は、より簡単に同様の操作を確認可能です。

まず、chromeでコマンドを実行できるようにするためにプログラム本体へのエイリアスを作成します。

$ alias chrome="/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome"

あとは同様にヘッドレスChromeとして実行します。

$ chrome --headless --disable-gpu --virtual-time-budget=100 --dump-dom https://example.com/

20240826_naonishi_headless-chrome-on-al2023_6.jpg

Macの環境ではUPower関連のエラーが出力されなかったため、標準エラー出力を捨てる処理は不要でした。

まとめ

スクレイピングなどでSeleniumを使う際にヘッドレスChrome(Chromium)と連携させるというように、コード内でヘッドレスブラウザを使用することの方が多いかとは思いますが、今回はcurlコマンドのように使うには?というイメージで確認してみました。

本記事がどなたかのお役に立てれば幸いです。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.